home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / transmission / web / javascript / torrent.js < prev    next >
Encoding:
JavaScript  |  2009-03-30  |  17.3 KB  |  590 lines

  1. /*
  2.  *    Copyright ¬© Dave Perrett and Malcolm Jarvis
  3.  *    This code is licensed under the GPL version 2.
  4.  *    For details, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  5.  *
  6.  *    Class Torrent
  7.  */
  8.  
  9. function Torrent(controller,data) {
  10.     this.initialize(controller,data);
  11.  
  12. // Constants
  13. Torrent._StatusWaitingToCheck  = 1;
  14. Torrent._StatusChecking        = 2;
  15. Torrent._StatusDownloading     = 4;
  16. Torrent._StatusSeeding         = 8;
  17. Torrent._StatusPaused          = 16;
  18. Torrent._InfiniteTimeRemaining = 215784000; // 999 Hours - may as well be infinite
  19.  
  20. Torrent.prototype =
  21. {
  22.     /*
  23.      * Constructor
  24.      */
  25.     initialize: function(controller,data)
  26.     {
  27.         // Create a new <li> element
  28.         var element = $('<li/>');
  29.         element.addClass('torrent');
  30.         element[0].id = 'torrent_' + data.id;
  31.         element._torrent = this;
  32.         this._element = element;
  33.         this._controller = controller;
  34.         controller._rows.push( element );
  35.         
  36.         // Create the 'name' <div>
  37.         var e = $('<div/>');
  38.         e.addClass('torrent_name');
  39.         element.append( e );
  40.         element._name_container = e;
  41.         
  42.         // Create the 'progress details' <div>
  43.         e = $('<div/>');
  44.         e.addClass('torrent_progress_details');
  45.         element.append(e);
  46.         element._progress_details_container = e;
  47.  
  48.         // Create the 'in progress' bar
  49.         e = $('<div/>');
  50.         e.addClass('torrent_progress_bar incomplete');
  51.         e.css('width', '0%');
  52.         element.append( e );
  53.         element._progress_complete_container = e;
  54.             
  55.         // Create the 'incomplete' bar (initially hidden)
  56.         e = $('<div/>');
  57.         e.addClass('torrent_progress_bar incomplete');
  58.         e.hide();
  59.         element.append( e );
  60.         element._progress_incomplete_container = e;
  61.         
  62.         // Add the pause/resume button - don't specify the
  63.         // image or alt text until the 'refresh()' function
  64.         // (depends on torrent state)
  65.         var image = $('<div/>');
  66.         image.addClass('torrent_pause');
  67.         e = $('<a/>');
  68.         e.append( image );
  69.         element.append( e );
  70.         element._pause_resume_button_image = image;
  71.         element._pause_resume_button = e;
  72.         if (!iPhone) e.bind('click', {element: element}, this.clickPauseResumeButton);
  73.         
  74.         // Create the 'peer details' <div>
  75.         e = $('<div/>');
  76.         e.addClass('torrent_peer_details');
  77.         element.append( e );
  78.         element._peer_details_container = e;
  79.             
  80.         // Set the torrent click observer
  81.         element.bind('click', {element: element}, this.clickTorrent);
  82.         if (!iPhone) element.bind('contextmenu', {element: element}, this.rightClickTorrent);        
  83.         
  84.         // Safari hack - first torrent needs to be moved down for some reason. Seems to be ok when
  85.         // using <li>'s in straight html, but adding through the DOM gets a bit odd.
  86.         if ($.browser.safari)
  87.             this._element.css('margin-top', '7px');
  88.         
  89.         // insert the element
  90.         $('#torrent_list').append(this._element);
  91.         
  92.         // Update all the labels etc
  93.         this.refresh(data);
  94.     },
  95.  
  96.  
  97.     /*--------------------------------------------
  98.      * 
  99.      *  S E T T E R S   /   G E T T E R S
  100.      * 
  101.      *--------------------------------------------*/
  102.     
  103.     /* Return the DOM element for this torrent (a <LI> element) */
  104.     element: function() {
  105.         return this._element;
  106.     },
  107.  
  108.     setElement: function( element ) {
  109.         this._element = element;
  110.         element._torrent = this;
  111.         this.refreshHTML( );
  112.     },
  113.  
  114.     activity: function() { return this._download_speed + this._upload_speed; },
  115.     comment: function() { return this._comment; },
  116.     completed: function() { return this._completed; },
  117.     creator: function() { return this._creator; },
  118.     dateAdded: function() { return this._date; },
  119.     downloadSpeed: function() { return this._download_speed; },
  120.     downloadTotal: function() { return this._download_total; },
  121.     errorMessage: function() { return this._error_message; },
  122.     hash: function() { return this._hashString; },
  123.     id: function() { return this._id; },
  124.     isActive: function() { return this.state() != Torrent._StatusPaused; },
  125.     isDownloading: function() { return this.state() == Torrent._StatusDownloading; },
  126.     isSeeding: function() { return this.state() == Torrent._StatusSeeding; },
  127.     name: function() { return this._name; },
  128.     peersSendingToUs: function() { return this._peers_sending_to_us; },
  129.     peersGettingFromUs: function() { return this._peers_getting_from_us; },
  130.     getPercentDone: function() {
  131.         if( !this._sizeWhenDone ) return 1.0;
  132.         if( !this._leftUntilDone ) return 1.0;
  133.         return ( this._sizeWhenDone - this._leftUntilDone )
  134.                / this._sizeWhenDone;
  135.     },
  136.     getPercentDoneStr: function() {
  137.         return Math.ratio( 100 * ( this._sizeWhenDone - this._leftUntilDone ),
  138.                                    this._sizeWhenDone );
  139.     },
  140.     size: function() { return this._size; },
  141.     state: function() { return this._state; },
  142.     stateStr: function() {
  143.         switch( this.state() ) {
  144.             case Torrent._StatusSeeding:        return 'Seeding';
  145.             case Torrent._StatusDownloading:    return 'Downloading';
  146.             case Torrent._StatusPaused:         return 'Paused';
  147.             case Torrent._StatusChecking:       return 'Verifying local data';
  148.             case Torrent._StatusWaitingToCheck: return 'Waiting to verify';
  149.             default:                            return 'error';
  150.         }
  151.     },
  152.     swarmSpeed: function() { return this._swarm_speed; },
  153.     totalLeechers: function() { return this._total_leechers; },
  154.     totalSeeders: function() { return this._total_seeders; },
  155.     uploadSpeed: function() { return this._upload_speed; },
  156.     uploadTotal: function() { return this._upload_total; },
  157.  
  158.     /*--------------------------------------------
  159.      * 
  160.      *  E V E N T   F U N C T I O N S
  161.      * 
  162.      *--------------------------------------------*/
  163.     
  164.     /*
  165.      * Process a right-click event on this torrent
  166.      */
  167.     rightClickTorrent: function(event)
  168.     {
  169.         // don't stop the event! need it for the right-click menu
  170.         
  171.         var t = event.data.element._torrent;
  172.         if ( !t.isSelected( ) )
  173.             t._controller.setSelectedTorrent( t );
  174.     },
  175.     
  176.     /*
  177.      * Process a click event on this torrent
  178.      */
  179.     clickTorrent: function( event )
  180.     {
  181.         // Prevents click carrying to parent element
  182.         // which deselects all on click
  183.         event.stopPropagation();
  184.         var torrent = event.data.element._torrent;
  185.         
  186.         // 'Apple' button emulation on PC :
  187.         // Need settable meta-key and ctrl-key variables for mac emulation
  188.         var meta_key = event.metaKey
  189.         var ctrl_key = event.ctrlKey
  190.         if (event.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) {
  191.             meta_key = true;
  192.             ctrl_key = false;
  193.         }
  194.         
  195.         // Shift-Click - Highlight a range between this torrent and the last-clicked torrent
  196.         if (iPhone) {
  197.             torrent._controller.setSelectedTorrent( torrent, true );
  198.         
  199.         } else if (event.shiftKey) {
  200.             torrent._controller.selectRange( torrent, true );
  201.             // Need to deselect any selected text
  202.             window.focus();
  203.         
  204.         // Apple-Click, not selected
  205.         } else if (!torrent.isSelected() && meta_key) {
  206.             torrent._controller.selectTorrent( torrent, true );
  207.             
  208.         // Regular Click, not selected
  209.         } else if (!torrent.isSelected()) {
  210.             torrent._controller.setSelectedTorrent( torrent, true );
  211.         
  212.         // Apple-Click, selected    
  213.         } else if (torrent.isSelected() && meta_key) {
  214.             torrent._controller.deselectTorrent( torrent, true );
  215.             
  216.         // Regular Click, selected
  217.         } else if (torrent.isSelected()) {
  218.             torrent._controller.setSelectedTorrent( torrent, true );
  219.         }
  220.         
  221.         torrent._controller.setLastTorrentClicked(torrent);
  222.     },
  223.  
  224.     /*
  225.      * Process a click event on the pause/resume button
  226.      */
  227.     clickPauseResumeButton: function( event )
  228.     {
  229.         // prevent click event resulting in selection of torrent
  230.         event.stopPropagation();
  231.  
  232.         // either stop or start the torrent
  233.         var torrent = event.data.element._torrent;
  234.         if( torrent.isActive( ) )
  235.             torrent._controller.stopTorrent( torrent );
  236.         else
  237.             torrent._controller.startTorrent( torrent );
  238.     },
  239.  
  240.     /*--------------------------------------------
  241.      * 
  242.      *  I N T E R F A C E   F U N C T I O N S
  243.      * 
  244.      *--------------------------------------------*/
  245.     
  246.     refresh: function(data) {
  247.         this.refreshData( data );
  248.         this.refreshHTML( );
  249.     },
  250.  
  251.     /*
  252.      * Refresh display
  253.      */
  254.     refreshData: function(data)
  255.     {
  256.         // These variables never change after the inital load
  257.         if (data.isPrivate)     this._is_private    = data.isPrivate;
  258.         if (data.hashString)    this._hashString    = data.hashString;
  259.         if (data.addedDate)     this._date          = data.addedDate;
  260.         if (data.totalSize)     this._size          = data.totalSize;
  261.         if (data.announceURL)   this._tracker       = data.announceURL;
  262.         if (data.comment)       this._comment       = data.comment;
  263.         if (data.creator)       this._creator       = data.creator;
  264.         if (data.dateCreated)   this._creator_date  = data.dateCreated;
  265.                 if (data.leftUntilDone) this._leftUntilDone = data.leftUntilDone;
  266.                 if (data.sizeWhenDone)  this._sizeWhenDone  = data.sizeWhenDone;
  267.         if (data.path)          this._torrent_file  = data.path;//FIXME
  268.         if (data.name) {
  269.             this._name = data.name;
  270.             this._name_lc = this._name.toLowerCase( );
  271.         }
  272.         
  273.         // Set the regularly-changing torrent variables
  274.         this._id                    = data.id;
  275.         this._completed             = data.haveUnchecked + data.haveValid;
  276.         this._verified              = data.haveValid;
  277.         this._download_total        = data.downloadedEver;
  278.         this._upload_total          = data.uploadedEver;
  279.         this._download_speed        = data.rateDownload;
  280.         this._upload_speed          = data.rateUpload;
  281.         this._peers_connected       = data.peersConnected;
  282.         this._peers_getting_from_us = data.peersGettingFromUs;
  283.         this._peers_sending_to_us   = data.peersSendingToUs;
  284.         this._error                 = data.error;
  285.         this._error_message         = data.errorString;
  286.         this._eta                   = data.eta;
  287.         this._swarm_speed           = data.swarmSpeed;
  288.         this._total_leechers        = Math.max( 0, data.leechers );
  289.         this._total_seeders         = Math.max( 0, data.seeders );
  290.         this._state                 = data.status;
  291.     },
  292.  
  293.     refreshHTML: function()
  294.     {
  295.         var progress_details;
  296.         var peer_details;
  297.         var root = this._element;
  298.         var MaxBarWidth = 100; // reduce this to make the progress bar shorter (%)
  299.         
  300.         setInnerHTML( root._name_container[0], this._name );
  301.         
  302.         // Add the progress bar
  303.                 var notDone = this._leftUntilDone > 0;
  304.  
  305.         // Fix for situation
  306.         // when a verifying/downloading torrent gets state seeding
  307.         if( this._state == Torrent._StatusSeeding )
  308.             notDone = false ;
  309.         
  310.         if( notDone )
  311.         {
  312.             var eta = '';
  313.             
  314.             if( this.isActive( ) )
  315.             {
  316.                 eta = '-';
  317.                 if (this._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining )
  318.                     eta += 'remaining time unknown';
  319.                 else
  320.                     eta += Math.formatSeconds(this._eta) + ' remaining';
  321.             }
  322.             
  323.             // Create the 'progress details' label
  324.             // Eg: '101 MB of 631 MB (16.02%) - 2 hr remaining'
  325.             progress_details = Math.formatBytes( this._sizeWhenDone - this._leftUntilDone )
  326.                              + ' of '
  327.                              + Math.formatBytes( this._sizeWhenDone )
  328.                              + ' ('
  329.                              + this.getPercentDoneStr()
  330.                              + '%)'
  331.                              + eta;
  332.         
  333.             // Figure out the percent completed
  334.             var css_completed_width = Math.floor( this.getPercentDone() * MaxBarWidth );
  335.             
  336.             // Update the 'in progress' bar
  337.             var class_name = this.isActive() ? 'in_progress' : 'incomplete_stopped';
  338.             var e = root._progress_complete_container;
  339.             e.removeClass( );
  340.             e.addClass( 'torrent_progress_bar' );
  341.             e.addClass( class_name );
  342.             e.css( 'width', css_completed_width + '%' );
  343.             if(css_completed_width == 0) { e.addClass( 'empty' ); }
  344.             
  345.             // Update the 'incomplete' bar
  346.             e = root._progress_incomplete_container;
  347.             if( !e.is('.incomplete')) {
  348.                 e.removeClass();
  349.                 e.addClass('torrent_progress_bar in_progress');
  350.             }
  351.             e.css('width', (MaxBarWidth - css_completed_width) + '%');
  352.             e.show();
  353.             
  354.             // Create the 'peer details' label
  355.             // Eg: 'Downloading from 36 of 40 peers - DL: 60.2 KB/s UL: 4.3 KB/s'
  356.             if( !this.isDownloading( ) )
  357.                 peer_details = this.stateStr( );
  358.             else {
  359.                 peer_details = 'Downloading from '
  360.                              + this.peersSendingToUs()
  361.                              + ' of '
  362.                              + this._peers_connected
  363.                              + ' peers - DL: '
  364.                              + Math.formatBytes(this._download_speed)
  365.                              + '/s UL: '
  366.                              + Math.formatBytes(this._upload_speed)
  367.                              + '/s';
  368.             }
  369.             
  370.         }
  371.         else
  372.         {
  373.             // Update the 'in progress' bar
  374.             var class_name = (this.isActive()) ? 'complete' : 'complete_stopped';
  375.             var e = root._progress_complete_container;
  376.             e.removeClass();
  377.             e.addClass('torrent_progress_bar ' + class_name );
  378.             
  379.             // Create the 'progress details' label
  380.             // Eg: '698.05 MB, uploaded 8.59 GB (Ratio: 12.3)'
  381.             progress_details = Math.formatBytes( this._size )
  382.                              + ', uploaded '
  383.                              + Math.formatBytes( this._upload_total )
  384.                              + ' (Ratio '
  385.                              + Math.ratio( this._upload_total, this._download_total )
  386.                              + ')';
  387.             
  388.             // Hide the 'incomplete' bar
  389.             root._progress_incomplete_container.hide();
  390.             
  391.             // Set progress to maximum
  392.             root._progress_complete_container.css('width', MaxBarWidth + '%');
  393.             
  394.             // Create the 'peer details' label
  395.             // Eg: 'Seeding to 13 of 22 peers - UL: 36.2 KB/s'
  396.             if( !this.isSeeding( ) )
  397.                 peer_details = this.stateStr( );
  398.             else
  399.                 peer_details = 'Seeding to '
  400.                              + this.peersGettingFromUs()
  401.                              + ' of '
  402.                              + this._peers_connected
  403.                              + ' peers - UL: '
  404.                              + Math.formatBytes(this._upload_speed)
  405.                              + '/s';
  406.         }
  407.         
  408.         // Update the progress details
  409.         setInnerHTML( root._progress_details_container[0], progress_details );
  410.         
  411.         // Update the peer details and pause/resume button
  412.         e = root._pause_resume_button_image[0];
  413.         if ( this.state() == Torrent._StatusPaused ) {
  414.             e.alt = 'Resume';
  415.             e.className = "torrent_resume";
  416.         } else {
  417.             e.alt = 'Pause';
  418.             e.className = "torrent_pause";
  419.         }
  420.         
  421.         if( this._error_message &&
  422.             this._error_message != '' &&
  423.             this._error_message != 'other' ) {
  424.             peer_details = this._error_message;
  425.         }
  426.         
  427.         setInnerHTML( root._peer_details_container[0], peer_details );
  428.     },
  429.  
  430.     /*
  431.      * Return true if this torrent is selected
  432.      */
  433.     isSelected: function() {
  434.         var e = this.element( );
  435.         return e && $.className.has( e[0], 'selected' );
  436.     },
  437.  
  438.     /**
  439.      * @param filter one of Prefs._Filter*
  440.      * @param search substring to look for, or null
  441.      * @return true if it passes the test, false if it fails
  442.      */
  443.     test: function( filter, search )
  444.     {
  445.         var pass = false;
  446.         
  447.         switch( filter )
  448.         {
  449.             case Prefs._FilterSeeding:
  450.                 pass = this.isSeeding();
  451.                 break;
  452.             case Prefs._FilterDownloading:
  453.                 pass = this.isDownloading();
  454.                 break;
  455.             case Prefs._FilterPaused:
  456.                 pass = !this.isActive();
  457.                 break;
  458.             default:
  459.                 pass = true;
  460.                 break;
  461.         }
  462.         
  463.         if( !pass )
  464.             return false;
  465.         
  466.         if( !search || !search.length )
  467.             return pass;
  468.         
  469.         var pos = this._name_lc.indexOf( search.toLowerCase() );
  470.         pass = pos != -1;
  471.         return pass;
  472.     }
  473. };
  474.  
  475. /** Helper function for Torrent.sortTorrents(). */
  476. Torrent.compareById = function( a, b ) {
  477.     return a.id() - b.id();
  478. };
  479.  
  480. /** Helper function for sortTorrents(). */
  481. Torrent.compareByAge = function( a, b ) {
  482.     return a.dateAdded() - b.dateAdded();
  483. };
  484.  
  485. /** Helper function for sortTorrents(). */
  486. Torrent.compareByName = function( a, b ) {
  487.     return a._name_lc.compareTo( b._name_lc );
  488. };
  489.  
  490. /** Helper function for sortTorrents(). */
  491. Torrent.compareByTracker = function( a, b ) {
  492.     return a._tracker.compareTo( b._tracker );
  493. };
  494.  
  495. /** Helper function for sortTorrents(). */
  496. Torrent.compareByState = function( a, b ) {
  497.     return a.state() - b.state();
  498. };
  499.  
  500. /** Helper function for sortTorrents(). */
  501. Torrent.compareByActivity = function( a, b ) {
  502.     return a.activity() - b.activity();
  503. };
  504.  
  505. /** Helper function for sortTorrents(). */
  506. Torrent.compareByProgress = function( a, b ) {
  507.     if( a.getPercentDone() !== b.getPercentDone() )
  508.         return a.getPercentDone() - b.getPercentDone();
  509.     var a_ratio = Math.ratio( a._upload_total, a._download_total );
  510.     var b_ratio = Math.ratio( b._upload_total, b._download_total );
  511.     return a_ratio - b_ratio;
  512. }
  513.  
  514. /**
  515.  * @param torrents an array of Torrent objects
  516.  * @param sortMethod one of Prefs._SortBy*
  517.  * @param sortDirection Prefs._SortAscending or Prefs._SortDescending
  518.  */
  519. Torrent.sortTorrents = function( torrents, sortMethod, sortDirection )
  520. {
  521.     switch( sortMethod )
  522.     {
  523.         case Prefs._SortByActivity:
  524.             torrents.sort( this.compareByActivity );
  525.             break;
  526.         case Prefs._SortByAge:
  527.             torrents.sort( this.compareByAge );
  528.             break;
  529.         case Prefs._SortByQueue:
  530.             torrents.sort( this.compareById );
  531.             break;
  532.         case Prefs._SortByProgress:
  533.             torrents.sort( this.compareByProgress );
  534.             break;
  535.         case Prefs._SortByState:
  536.             torrents.sort( this.compareByState );
  537.             break;
  538.         case Prefs._SortByTracker:
  539.             torrents.sort( this.compareByTracker );
  540.             break;
  541.         case Prefs._SortByName:
  542.             torrents.sort( this.compareByName );
  543.             break;
  544.         default:
  545.             console.warn( "unknown sort method: " + sortMethod );
  546.             break;
  547.     }
  548.     
  549.     if( sortDirection == Prefs._SortDescending )
  550.         torrents.reverse( );
  551.     
  552.     return torrents;
  553. };
  554.  
  555. /**
  556.  * @brief fast binary search to find a torrent
  557.  * @param torrents an array of torrents sorted by Id
  558.  * @param id the id to search for
  559.  * @return the index, or -1
  560.  */
  561. Torrent.indexOf = function( torrents, id )
  562. {
  563.     var low = 0;
  564.     var high = torrents.length;
  565.     while( low < high ) {
  566.         var mid = Math.floor( ( low + high ) / 2 );
  567.         if( torrents[mid].id() < id )
  568.             low = mid + 1;
  569.         else
  570.             high = mid;
  571.     }
  572.     if( ( low < torrents.length ) && ( torrents[low].id() == id ) ) {
  573.         return low;
  574.     } else {
  575.         return -1; // not found
  576.     }
  577. };
  578.  
  579. /**
  580.  * @param torrents an array of torrents sorted by Id
  581.  * @param id the id to search for
  582.  * @return the torrent, or null
  583.  */
  584. Torrent.lookup = function( torrents, id )
  585. {
  586.     var pos = Torrent.indexOf( torrents, id );
  587.     return pos >= 0 ? torrents[pos] : null;
  588. };
  589.